/*
 * Description:  Common Utils in windows
 */

#include "text_utils.h"
#include <regex>
#include <fstream>
#include <shlwapi.h>
#include <sstream>
#include <iostream>
#include <filesystem>
#include "log.h"

namespace fs = std::filesystem;
namespace COMMON {
const int FILENAME_MAX_LEN = 255; // windows文件名长度
bool TextUtils::Cmds2File(const std::vector<std::string> &cmds, const std::string &filePath)
{
    LOG_INFO("Start generate file");
    std::ofstream ofs;
    ofs.open(filePath.c_str(), std::ios::ate|std::ios::out);
    if (!ofs.is_open()) {
        LOG_ERR("Can not generate update script");
        return false;
    }
    for (auto x:cmds) {
        ofs << x << "\r\n";
    }
    ofs.close();
    return true;
}

std::string TextUtils::UnicodeToUTF8(const std::wstring& value)
{
    int textLen = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr, 0, nullptr, nullptr);
    if (textLen <= 0) {
        return "";
    }
    char* elementText = new (std::nothrow) char[textLen + 1];
    if (elementText == nullptr) {
        return "";
    }
    SecureZeroMemory(reinterpret_cast<void*>(elementText), sizeof(char) * (textLen + 1));
    ::WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, elementText, textLen, nullptr, nullptr);
    std::string retVal = elementText;
    delete[] elementText;
    return retVal;
}

bool TextUtils::CheckIpAddr(const std::string &ip)
{
    std::regex ipv4("(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}"
        "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])");
    return std::regex_match(ip, ipv4);
}

bool TextUtils::IsValidFileName(const std::string &fileName)
{
    if (fileName.size() > FILENAME_MAX_LEN || fileName.empty()) {
        return false;
    }
    std::string arr[] = {"/", "\\", "?", "*", "<", ">", "\"", "|", " ", "&"};
    for (std::string str : arr) {
        if (fileName.find(str) != std::string::npos) {
            return false;
        }
    }
    return true;
}

bool TextUtils::CheckSqlInput(const std::string &input)
{
    std::regex sqlKeywords(R"((\bSELECT\b|\bINSERT\b|\UPDATE\b|\bDROP\b|\bDELETE\b|\bUNION\b|\bOR\b|"
        "\bAND\b|\WHERE\b|\bFROM\b|\bJOIN\b|\bTABLE\b|\bDATABASE\b))", std::regex_constants::icase);
    std::smatch matchResult;
    if (std::regex_search(input, matchResult, sqlKeywords)) {
        return false;
    }
    return true;
}

std::string TextUtils::UTF8ToGBK(const std::string &strUTF8)
{
    int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), -1, NULL, 0);
    wchar_t *wszGBK = new (std::nothrow) wchar_t[len + 1];
    if (wszGBK == nullptr) {
        LOG_ERR("alloc memary failed.");
        return "";
    }
    SecureZeroMemory(wszGBK, len + 1);
    MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), -1, wszGBK, len);
    len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
    char *szGBK = new (std::nothrow) char[len + 1];
    if (szGBK == nullptr) {
        LOG_ERR("alloc memary failed.");
        delete[] wszGBK;
        return "";
    }
    SecureZeroMemory(szGBK, len + 1);
    WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
    std::string res(szGBK);
    delete[] szGBK;
    delete[] wszGBK;
    return res;
}

bool TextUtils::FilePatchCheck(const std::string &filePath)
{
    if (filePath.empty()) {
        LOG_ERR("filePath is null");
        return false;
    }
    if (!FileExists(filePath)) {
        LOG_ERR("filePath is not exist");
        return false;
    }
    if ((filePath.find("..\\") != std::string::npos) || (filePath.find("../") != std::string::npos)) {
        return false;
    }
    int num = MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, NULL, 0);
    if (num <= 0) {
        LOG_ERR("filePath length is error");
        return false;
    }
    wchar_t *wide = new(std::nothrow) wchar_t[num];
    if (wide == nullptr) {
        LOG_ERR("filePath copy wide is null");
        return false;
    }
    MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, wide, num);
    if (PathIsNetworkPathW(wide)) {
        LOG_ERR("filePath is networkpath");
        delete[] wide;
        wide = nullptr;
        return false;
    }
    delete[] wide;
    wide = nullptr;
    return true;
}

bool TextUtils::FileExists(const std::string& filePath)
{
    WIN32_FILE_ATTRIBUTE_DATA attData = {0};
    bool ret = false;
    if (GetFileAttributesExA(filePath.c_str(), GetFileExInfoStandard, &attData) == TRUE) {
        ret = ((attData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
    }
    if(!ret){
        LOG_INFO("file not exist: {}", filePath);
    }
    return ret;
}

std::string TextUtils::PwcharToString(PWCHAR pwstr)
{
    if (!pwstr) {
        return std::string();
    }
    int size = WideCharToMultiByte(CP_UTF8, 0, pwstr, -1, nullptr, 0, nullptr, nullptr);
    if (size == 0) {
        return std::string();
    }
    std::string result(size, 0);
    WideCharToMultiByte(CP_UTF8, 0, pwstr, -1, &result[0], size, nullptr, nullptr);
    result.resize(size - 1); // 去除末尾的空字符
    return result;
}

bool TextUtils::CheckGuid(const std::string& guid)
{
    std::regex guidRegex(R"(\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\})");
    return std::regex_match(guid, guidRegex);
}

std::string TextUtils::WCharToUTF8(const wchar_t* wstr)
{
    if (wstr == nullptr) {
        LOG_ERR("wstr is null");
        return "[null]";
    }
    int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
    if (bufferSize > 0) {
        std::string utf8Str(bufferSize, 0);
        int result = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, utf8Str.data(), bufferSize, NULL, NULL);
        if (result > 0) {
            utf8Str.resize(bufferSize - 1);
            return utf8Str;
        }
    }
    LOG_ERR("WideCharToMultiByte failed. Last error: {}", GetLastError());
    return "[null]";
}

std::string TextUtils::HKEYToHexString(HKEY hKey)
{
    std::ostringstream oss;
    oss << "0x" << std::hex << reinterpret_cast<uintptr_t>(hKey);
    return oss.str();
}

std::string TextUtils::LPCSTRToUTF8(LPCSTR src)
{
    // 先转为宽字符，再转为UTF-8
    int wlen = MultiByteToWideChar(CP_ACP, 0, src, -1, nullptr, 0);
    if (wlen <= 0) {
        LOG_ERR("LPCSTRToUTF8 failed. Last error: {}", GetLastError());
        return "";
    }
    std::wstring wstr(wlen, L'\0');
    if (MultiByteToWideChar(CP_ACP, 0, src, -1, &wstr[0], wlen) <= 0) {
        LOG_ERR("LPCSTRToUTF8 failed. Last error: {}", GetLastError());
        return "";
    }
    int ulen = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
    if (ulen <= 0) {
        LOG_ERR("LPCSTRToUTF8 failed. Last error: {}", GetLastError());
        return "";
    }
    std::string utf8(ulen, '\0');
    if (WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &utf8[0], ulen, nullptr, nullptr) > 0) {
        return utf8;
    }
    LOG_ERR("ToUTF8 failed. Last error: {}", GetLastError());
    return "";
}

bool TextUtils::ReplaceLastChar(const std::string& filePath, const std::string& replacement)
{
    // 检查文件是否存在
    if (!fs::exists(filePath)) {
        LOG_ERR("file not exist: {}", filePath);
        return false;
    }

    // 检查文件是否为空
    if (fs::file_size(filePath) == 0) {
        LOG_ERR("file is empty: {}", filePath);
        return false;
    }

    // 读取文件内容
    std::ifstream file(filePath, std::ios::binary);
    if (!file) {
        LOG_ERR("file connot open: {}", filePath);
        return false;
    }

    // 获取文件大小并定位到倒数第二个字符（最后一个字符的前一个位置）
    file.seekg(0, std::ios::end);
    std::streamsize size = file.tellg();
    file.seekg(size - 1, std::ios::beg);

    // 读取除最后一个字符外的所有内容
    std::string content(size - 1, '\0');
    file.seekg(0, std::ios::beg);
    file.read(content.data(), size - 1);
    file.close();

    // 将修改后的内容写回文件
    std::ofstream outFile(filePath, std::ios::binary);
    if (!outFile) {
        LOG_ERR("cannot open file write");
        return false;
    }
    outFile << content << replacement;
    if (!outFile) {
        LOG_ERR("write file failed");
        outFile.close();
        return false;
    }
    outFile.close();
    LOG_INFO("ReplaceLastChar success");
    return true;
}
}